home *** CD-ROM | disk | FTP | other *** search
/ InterCD 2000 September / september_2000.iso / intercd / root / ^Linux / WindowMaker / src / session.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-03-29  |  34.3 KB  |  1,388 lines

  1. /* session.c - session state handling and R6 style session management
  2.  *
  3.  *  Copyright (c) 1998 Dan Pascu
  4.  *  Copyright (c) 1998, 1999 Alfredo Kojima
  5.  *
  6.  *  Window Maker window manager
  7.  *
  8.  *  This program is free software; you can redistribute it and/or modify
  9.  *  it under the terms of the GNU General Public License as published by
  10.  *  the Free Software Foundation; either version 2 of the License, or
  11.  *  (at your option) any later version.
  12.  *
  13.  *  This program is distributed in the hope that it will be useful,
  14.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  *  GNU General Public License for more details.
  17.  *
  18.  *  You should have received a copy of the GNU General Public License
  19.  *  along with this program; if not, write to the Free Software
  20.  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
  21.  *  USA.
  22.  */
  23.  
  24.  
  25. /*
  26.  * 
  27.  * If defined(XSMP_ENABLED) and session manager is running then
  28.  *     do normal stuff
  29.  * else
  30.  *     do pre-R6 session management stuff (save window state and relaunch)
  31.  *
  32.  * When doing a checkpoint:
  33.  * 
  34.  * = Without XSMP
  35.  * Open "Stop"/status Dialog
  36.  * Send SAVE_YOURSELF to clients and wait for reply
  37.  * Save restart info
  38.  * Save state of clients
  39.  * 
  40.  * = With XSMP
  41.  * Send checkpoint request to sm
  42.  *
  43.  * When exiting:
  44.  * -------------
  45.  * 
  46.  * = Without XSMP
  47.  * 
  48.  * Open "Exit Now"/status Dialog
  49.  * Send SAVE_YOURSELF to clients and wait for reply
  50.  * Save restart info
  51.  * Save state of clients
  52.  * Send DELETE to all clients
  53.  * When no more clients are left or user hit "Exit Now", exit
  54.  * 
  55.  * = With XSMP
  56.  *
  57.  * Send Shutdown request to session manager
  58.  * if SaveYourself message received, save state of clients 
  59.  * if the Die message is received, exit.
  60.  */
  61.  
  62. #include "wconfig.h"
  63.  
  64. #include <X11/Xlib.h>
  65. #include <X11/Xutil.h>
  66.  
  67. #ifdef XSMP_ENABLED
  68. #include <X11/SM/SMlib.h>
  69. #endif
  70.  
  71. #include <stdlib.h>
  72. #include <stdio.h>
  73. #include <string.h>
  74. #include <unistd.h>
  75. #include <time.h>
  76.  
  77.  
  78. #include "WindowMaker.h"
  79. #include "screen.h"
  80. #include "window.h"
  81. #include "client.h"
  82. #include "session.h"
  83. #include "wcore.h"
  84. #include "framewin.h"
  85. #include "workspace.h"
  86. #include "funcs.h"
  87. #include "properties.h"
  88. #include "application.h"
  89. #include "appicon.h"
  90.  
  91.  
  92. #include "dock.h"
  93.  
  94.  
  95. #include <proplist.h>
  96.  
  97. /** Global **/
  98.  
  99. extern Atom _XA_WM_SAVE_YOURSELF;
  100.  
  101. extern Time LastTimestamp;
  102.  
  103. #ifdef XSMP_ENABLED
  104.  
  105. extern int wScreenCount;
  106.  
  107. /* requested for SaveYourselfPhase2 */
  108. static Bool sWaitingPhase2 = False;
  109.  
  110. static SmcConn sSMCConn = NULL;
  111.  
  112. static WMHandlerID sSMInputHandler = NULL;
  113.  
  114. /* our SM client ID */
  115. static char *sClientID = NULL;
  116. #endif
  117.  
  118.  
  119. static proplist_t sApplications = NULL;
  120. static proplist_t sCommand;
  121. static proplist_t sName;
  122. static proplist_t sHost;
  123. static proplist_t sWorkspace;
  124. static proplist_t sShaded;
  125. static proplist_t sMiniaturized;
  126. static proplist_t sHidden;
  127. static proplist_t sGeometry;
  128. static proplist_t sShortcutMask;
  129.  
  130. static proplist_t sDock;
  131.  
  132. static proplist_t sYes, sNo;
  133.  
  134.  
  135. static void
  136. make_keys()
  137. {
  138.     if (sApplications!=NULL)
  139.         return;
  140.  
  141.     sApplications = PLMakeString("Applications");
  142.     sCommand = PLMakeString("Command");
  143.     sName = PLMakeString("Name");
  144.     sHost = PLMakeString("Host");
  145.     sWorkspace = PLMakeString("Workspace");
  146.     sShaded = PLMakeString("Shaded");
  147.     sMiniaturized = PLMakeString("Miniaturized");
  148.     sHidden = PLMakeString("Hidden");
  149.     sGeometry = PLMakeString("Geometry");
  150.     sDock = PLMakeString("Dock");
  151.     sShortcutMask = PLMakeString("ShortcutMask");
  152.  
  153.     sYes = PLMakeString("Yes");
  154.     sNo = PLMakeString("No");
  155. }
  156.  
  157.  
  158.  
  159. static int 
  160. getBool(proplist_t value)
  161. {
  162.     char *val;
  163.  
  164.     if (!PLIsString(value)) {
  165.         return 0;
  166.     }
  167.     if (!(val = PLGetString(value))) {
  168.         return 0;
  169.     }
  170.  
  171.     if ((val[1]=='\0' && (val[0]=='y' || val[0]=='Y'))
  172.     || strcasecmp(val, "YES")==0) {
  173.  
  174.     return 1;
  175.     } else if ((val[1]=='\0' && (val[0]=='n' || val[0]=='N'))
  176.          || strcasecmp(val, "NO")==0) {
  177.     return 0;
  178.     } else {
  179.     int i;
  180.     if (sscanf(val, "%i", &i)==1) {
  181.         return (i!=0);
  182.     } else {
  183.         wwarning(_("can't convert \"%s\" to boolean"), val);
  184.         return 0;
  185.     }
  186.     }
  187. }
  188.  
  189.  
  190. static unsigned
  191. getInt(proplist_t value)
  192. {
  193.     char *val;
  194.     unsigned n;
  195.     
  196.     if (!PLIsString(value))
  197.     return 0;
  198.     val = PLGetString(value);
  199.     if (!val)
  200.     return 0;
  201.     if (sscanf(val, "%u", &n) != 1)
  202.     return 0;
  203.  
  204.     return n;
  205. }
  206.  
  207.  
  208.  
  209. static proplist_t
  210. makeWindowState(WWindow *wwin, WApplication *wapp)
  211. {
  212.     WScreen *scr = wwin->screen_ptr;
  213.     Window win;
  214.     int argc;
  215.     char **argv;
  216.     int i;
  217.     unsigned mask;
  218.     char *class, *instance, *command=NULL, buffer[256];
  219.     proplist_t win_state, cmd, name, workspace;
  220.     proplist_t shaded, miniaturized, hidden, geometry;
  221.     proplist_t dock, shortcut;
  222.  
  223.     if (wwin->main_window!=None && wwin->main_window!=wwin->client_win)
  224.         win = wwin->main_window;
  225.     else
  226.         win = wwin->client_win;
  227.  
  228.     if (XGetCommand(dpy, win, &argv, &argc) && argc>0) {
  229.         command = FlattenStringList(argv, argc);
  230.         XFreeStringList(argv);
  231.     }
  232.     if (!command)
  233.         return NULL;
  234.  
  235.     if (PropGetWMClass(win, &class, &instance)) {
  236.         if (class && instance)
  237.             sprintf(buffer, "%s.%s", instance, class);
  238.         else if (instance)
  239.             sprintf(buffer, "%s", instance);
  240.         else if (class)
  241.             sprintf(buffer, ".%s", class);
  242.         else
  243.             sprintf(buffer, ".");
  244.  
  245.         name = PLMakeString(buffer);
  246.         cmd = PLMakeString(command);
  247.         /*sprintf(buffer, "%d", wwin->frame->workspace+1);
  248.         workspace = PLMakeString(buffer);*/
  249.         workspace = PLMakeString(scr->workspaces[wwin->frame->workspace]->name);
  250.         shaded = wwin->flags.shaded ? sYes : sNo;
  251.         miniaturized = wwin->flags.miniaturized ? sYes : sNo;
  252.         hidden = wwin->flags.hidden ? sYes : sNo;
  253.         sprintf(buffer, "%ix%i+%i+%i", wwin->client.width, wwin->client.height,
  254.         wwin->frame_x, wwin->frame_y);
  255.         geometry = PLMakeString(buffer);
  256.     
  257.     for (mask = 0, i = 0; i < MAX_WINDOW_SHORTCUTS; i++) {
  258.         if (scr->shortcutWindows[i] != NULL &&
  259.         WMGetFirstInBag(scr->shortcutWindows[i], wwin) != WBNotFound) {
  260.         mask |= 1<<i;
  261.         }
  262.     }
  263.  
  264.     sprintf(buffer, "%u", mask);
  265.     shortcut = PLMakeString(buffer);
  266.  
  267.         win_state = PLMakeDictionaryFromEntries(sName, name,
  268.                                                 sCommand, cmd,
  269.                                                 sWorkspace, workspace,
  270.                                                 sShaded, shaded,
  271.                                                 sMiniaturized, miniaturized,
  272.                                                 sHidden, hidden,
  273.                         sShortcutMask, shortcut,
  274.                                                 sGeometry, geometry,
  275.                                                 NULL);
  276.  
  277.         PLRelease(name);
  278.         PLRelease(cmd);
  279.         PLRelease(workspace);
  280.         PLRelease(geometry);
  281.     PLRelease(shortcut);
  282.         if (wapp && wapp->app_icon && wapp->app_icon->dock) {
  283.             int i;
  284.             char *name;
  285.             if (wapp->app_icon->dock == scr->dock) {
  286.                 name="Dock";
  287.             } else {
  288.                 for(i=0; i<scr->workspace_count; i++)
  289.                     if(scr->workspaces[i]->clip == wapp->app_icon->dock)
  290.                         break;
  291.                 assert( i < scr->workspace_count);
  292.                 /*n = i+1;*/
  293.                 name = scr->workspaces[i]->name;
  294.             }
  295.             dock = PLMakeString(name);
  296.             PLInsertDictionaryEntry(win_state, sDock, dock);
  297.             PLRelease(dock);
  298.         }
  299.     } else {
  300.         win_state = NULL;
  301.     }
  302.  
  303.     if (instance) XFree(instance);
  304.     if (class) XFree(class);
  305.     if (command) free(command);
  306.  
  307.     return win_state;
  308. }
  309.  
  310.  
  311. void
  312. wSessionSaveState(WScreen *scr)
  313. {
  314.     WWindow *wwin = scr->focused_window;
  315.     proplist_t win_info, wks;
  316.     proplist_t list=NULL;
  317.     WMBag *wapp_list=NULL;
  318.  
  319.  
  320.     make_keys();
  321.  
  322.     if (!scr->session_state) {
  323.         scr->session_state = PLMakeDictionaryFromEntries(NULL, NULL, NULL);
  324.         if (!scr->session_state)
  325.             return;
  326.     }
  327.  
  328.     list = PLMakeArrayFromElements(NULL);
  329.  
  330.     wapp_list = WMCreateBag(16);
  331.  
  332.     while (wwin) {
  333.         WApplication *wapp=wApplicationOf(wwin->main_window);
  334.  
  335.         if (wwin->transient_for==None 
  336.         && WMGetFirstInBag(wapp_list, wapp)==WBNotFound
  337.         && !WFLAGP(wwin, dont_save_session)) {
  338.             /* A entry for this application was not yet saved. Save one. */
  339.             if ((win_info = makeWindowState(wwin, wapp))!=NULL) {
  340.                 list = PLAppendArrayElement(list, win_info);
  341.                 PLRelease(win_info);
  342.                 /* If we were succesful in saving the info for this window
  343.                  * add the application the window belongs to, to the
  344.                  * application list, so no multiple entries for the same
  345.                  * application are saved.
  346.                  */
  347.         WMPutInBag(wapp_list, wapp);
  348.             }
  349.         }
  350.         wwin = wwin->prev;
  351.     }
  352.     PLRemoveDictionaryEntry(scr->session_state, sApplications);
  353.     PLInsertDictionaryEntry(scr->session_state, sApplications, list);
  354.     PLRelease(list);
  355.  
  356.     wks = PLMakeString(scr->workspaces[scr->current_workspace]->name);
  357.     PLInsertDictionaryEntry(scr->session_state, sWorkspace, wks);
  358.     PLRelease(wks);
  359.  
  360.     WMFreeBag(wapp_list);
  361. }
  362.  
  363.  
  364. void
  365. wSessionClearState(WScreen *scr)
  366. {
  367.     make_keys();
  368.  
  369.     if (!scr->session_state)
  370.         return;
  371.  
  372.     PLRemoveDictionaryEntry(scr->session_state, sApplications);
  373.     PLRemoveDictionaryEntry(scr->session_state, sWorkspace);
  374. }
  375.  
  376.  
  377. static pid_t
  378. execCommand(WScreen *scr, char *command, char *host)
  379. {
  380.     pid_t pid;
  381.     char **argv;
  382.     int argc;
  383.  
  384.     ParseCommand(command, &argv, &argc);
  385.  
  386.     if (argv==NULL) {
  387.         return 0;
  388.     }
  389.  
  390.     if ((pid=fork())==0) {
  391.         char **args;
  392.         int i;
  393.  
  394.     SetupEnvironment(scr);
  395.  
  396.         args = malloc(sizeof(char*)*(argc+1));
  397.         if (!args)
  398.             exit(111);
  399.         for (i=0; i<argc; i++) {
  400.             args[i] = argv[i];
  401.         }
  402.         args[argc] = NULL;
  403.         execvp(argv[0], args);
  404.         exit(111);
  405.     }
  406.     while (argc > 0)
  407.         free(argv[--argc]);
  408.     free(argv);
  409.     return pid;
  410. }
  411.  
  412.  
  413. static WSavedState*
  414. getWindowState(WScreen *scr, proplist_t win_state)
  415. {
  416.     WSavedState *state = wmalloc(sizeof(WSavedState));
  417.     proplist_t value;
  418.     char *tmp;
  419.     unsigned mask;
  420.     int i;
  421.  
  422.     memset(state, 0, sizeof(WSavedState));
  423.     state->workspace = -1;
  424.     value = PLGetDictionaryEntry(win_state, sWorkspace);
  425.     if (value && PLIsString(value)) {
  426.         tmp = PLGetString(value);
  427.         if (sscanf(tmp, "%i", &state->workspace)!=1) {
  428.             state->workspace = -1;
  429.             for (i=0; i < scr->workspace_count; i++) {
  430.                 if (strcmp(scr->workspaces[i]->name, tmp)==0) {
  431.                     state->workspace = i;
  432.                     break;
  433.                 }
  434.             }
  435.         } else {
  436.             state->workspace--;
  437.         }
  438.     }
  439.     if ((value = PLGetDictionaryEntry(win_state, sShaded))!=NULL)
  440.         state->shaded = getBool(value);
  441.     if ((value = PLGetDictionaryEntry(win_state, sMiniaturized))!=NULL)
  442.         state->miniaturized = getBool(value);
  443.     if ((value = PLGetDictionaryEntry(win_state, sHidden))!=NULL)
  444.         state->hidden = getBool(value);
  445.     if ((value = PLGetDictionaryEntry(win_state, sHidden))!=NULL) {
  446.         mask = getInt(value);
  447.     state->window_shortcuts = mask;
  448.     }
  449.  
  450.     value = PLGetDictionaryEntry(win_state, sGeometry);
  451.     if (value && PLIsString(value)) {
  452.         if (sscanf(PLGetString(value), "%ix%i+%i+%i",
  453.                    &state->w, &state->h, &state->x, &state->y)==4 &&
  454.             (state->w>0 && state->h>0)) {
  455.             state->use_geometry = 1;
  456.         } else if (sscanf(PLGetString(value), "%i,%i,%i,%i",
  457.                    &state->x, &state->y, &state->w, &state->h)==4 &&
  458.             (state->w>0 && state->h>0)) { 
  459.         /* TODO: remove redundant sscanf() in version 0.20.x */
  460.             state->use_geometry = 1;
  461.         }
  462.  
  463.     }
  464.  
  465.     return state;
  466. }
  467.  
  468.  
  469. #define SAME(x, y) (((x) && (y) && !strcmp((x), (y))) || (!(x) && !(y)))
  470.  
  471. void
  472. wSessionRestoreState(WScreen *scr)
  473. {
  474.     WSavedState *state;
  475.     char *instance, *class, *command, *host;
  476.     proplist_t win_info, apps, cmd, value;
  477.     pid_t pid;
  478.     int i, count;
  479.     WDock *dock;
  480.     WAppIcon *btn=NULL;
  481.     int j, n, found;
  482.     char *tmp;
  483.  
  484.     make_keys();
  485.  
  486.     if (!scr->session_state)
  487.         return;
  488.  
  489.     PLSetStringCmpHook(NULL);
  490.  
  491.     apps = PLGetDictionaryEntry(scr->session_state, sApplications);
  492.     if (!apps)
  493.         return;
  494.  
  495.     count = PLGetNumberOfElements(apps);
  496.     if (count==0) 
  497.         return;
  498.  
  499.     for (i=0; i<count; i++) {
  500.         win_info = PLGetArrayElement(apps, i);
  501.  
  502.         cmd = PLGetDictionaryEntry(win_info, sCommand);
  503.         if (!cmd || !PLIsString(cmd) || !(command = PLGetString(cmd))) {
  504.             continue;
  505.         }
  506.  
  507.         value = PLGetDictionaryEntry(win_info, sName);
  508.         if (!value)
  509.             continue;
  510.  
  511.         ParseWindowName(value, &instance, &class, "session");
  512.         if (!instance && !class)
  513.             continue;
  514.  
  515.         value = PLGetDictionaryEntry(win_info, sHost);
  516.         if (value && PLIsString(value))
  517.             host = PLGetString(value);
  518.         else
  519.             host = NULL;
  520.  
  521.         state = getWindowState(scr, win_info);
  522.  
  523.         dock = NULL;
  524.         value = PLGetDictionaryEntry(win_info, sDock);
  525.         if (value && PLIsString(value) && (tmp = PLGetString(value))!=NULL) {
  526.             if (sscanf(tmp, "%i", &n)!=1) {
  527.                 if (!strcasecmp(tmp, "DOCK")) {
  528.                     dock = scr->dock;
  529.                 } else {
  530.                     for (j=0; j < scr->workspace_count; j++) {
  531.                         if (strcmp(scr->workspaces[j]->name, tmp)==0) {
  532.                             dock = scr->workspaces[j]->clip;
  533.                             break;
  534.                         }
  535.                     }
  536.                 }
  537.             } else {
  538.                 if (n == 0) {
  539.                     dock = scr->dock;
  540.                 } else if (n>0 && n<=scr->workspace_count) {
  541.                     dock = scr->workspaces[n-1]->clip;
  542.                 }
  543.             }
  544.         }
  545.  
  546.         found = 0;
  547.         if (dock!=NULL) {
  548.             for (j=0; j<dock->max_icons; j++) {
  549.                 btn = dock->icon_array[j];
  550.                 if (btn && SAME(instance, btn->wm_instance) &&
  551.                     SAME(class, btn->wm_class) &&
  552.                     SAME(command, btn->command) &&
  553.             !btn->launching) {
  554.                     found = 1;
  555.                     break;
  556.                 }
  557.             }
  558.         }
  559.  
  560.         if (found) {
  561.             wDockLaunchWithState(dock, btn, state);
  562.         } else if ((pid = execCommand(scr, command, host)) > 0) {
  563.             wWindowAddSavedState(instance, class, command, pid, state);
  564.         } else {
  565.             free(state);
  566.         }
  567.  
  568.         if (instance) free(instance);
  569.         if (class) free(class);
  570.     }
  571.     /* clean up */
  572.     PLSetStringCmpHook(StringCompareHook);
  573. }
  574.  
  575.  
  576. void
  577. wSessionRestoreLastWorkspace(WScreen *scr)
  578. {
  579.     proplist_t wks;
  580.     int w, i;
  581.     char *tmp;
  582.  
  583.     make_keys();
  584.  
  585.     if (!scr->session_state)
  586.         return;
  587.  
  588.     PLSetStringCmpHook(NULL);
  589.  
  590.     wks = PLGetDictionaryEntry(scr->session_state, sWorkspace);
  591.     if (!wks || !PLIsString(wks))
  592.         return;
  593.  
  594.     tmp = PLGetString(wks);
  595.  
  596.     /* clean up */
  597.     PLSetStringCmpHook(StringCompareHook);
  598.  
  599.     if (sscanf(tmp, "%i", &w)!=1) {
  600.         w = -1;
  601.         for (i=0; i < scr->workspace_count; i++) {
  602.             if (strcmp(scr->workspaces[i]->name, tmp)==0) {
  603.                 w = i;
  604.                 break;
  605.             }
  606.         }
  607.     } else {
  608.         w--;
  609.     }
  610.  
  611.     if (w!=scr->current_workspace && w<scr->workspace_count) {
  612.         wWorkspaceChange(scr, w);
  613.     }
  614. }
  615.  
  616.  
  617. static void
  618. clearWaitingAckState(WScreen *scr)
  619. {
  620.     WWindow *wwin;
  621.     WApplication *wapp;
  622.  
  623.     for (wwin = scr->focused_window; wwin != NULL; wwin = wwin->prev) {
  624.     wwin->flags.waiting_save_ack = 0;
  625.     if (wwin->main_window != None) {
  626.         wapp = wApplicationOf(wwin->main_window);
  627.         if (wapp)
  628.         wapp->main_window_desc->flags.waiting_save_ack = 0;
  629.     }
  630.     }
  631. }
  632.  
  633.  
  634. void
  635. wSessionSaveClients(WScreen *scr)
  636. {
  637.     
  638. }
  639.             
  640.  
  641. /*
  642.  * With XSMP, this job is done by smproxy
  643.  */
  644. void
  645. wSessionSendSaveYourself(WScreen *scr)
  646. {
  647.     WWindow *wwin;
  648.     int count;
  649.  
  650.     /* freeze client interaction with clients */
  651.     XGrabKeyboard(dpy, scr->root_win, False, GrabModeAsync, GrabModeAsync,
  652.           CurrentTime);
  653.     XGrabPointer(dpy, scr->root_win, False, ButtonPressMask|ButtonReleaseMask,
  654.          GrabModeAsync, GrabModeAsync, scr->root_win, None,
  655.          CurrentTime);
  656.  
  657.     clearWaitingAckState(scr);
  658.  
  659.     count = 0;
  660.  
  661.     /* first send SAVE_YOURSELF for everybody */
  662.     for (wwin = scr->focused_window; wwin != NULL; wwin = wwin->prev) {
  663.     WWindow *mainWin;
  664.  
  665.     mainWin = wWindowFor(wwin->main_window);
  666.  
  667.     if (mainWin) {
  668.         /* if the client is a multi-window client, only send message
  669.          * to the main window */
  670.         wwin = mainWin;
  671.     }
  672.  
  673.     /* make sure the SAVE_YOURSELF flag is up-to-date */
  674.     PropGetProtocols(wwin->client_win, &wwin->protocols);
  675.  
  676.     if (wwin->protocols.SAVE_YOURSELF) {
  677.         if (!wwin->flags.waiting_save_ack) {
  678.         wClientSendProtocol(wwin, _XA_WM_SAVE_YOURSELF, LastTimestamp);
  679.  
  680.         wwin->flags.waiting_save_ack = 1;
  681.         count++;
  682.         }
  683.     } else {
  684.         wwin->flags.waiting_save_ack = 0;
  685.     }
  686.     }
  687.  
  688.     /* then wait for acknowledge */
  689.     while (count > 0) {
  690.     
  691.     }
  692.  
  693.     XUngrabPointer(dpy, CurrentTime);
  694.     XUngrabKeyboard(dpy, CurrentTime);
  695.     XFlush(dpy);
  696. }
  697.  
  698.  
  699. #ifdef XSMP_ENABLED
  700. /*
  701.  * With full session management support, the part of WMState 
  702.  * that store client window state will become obsolete (maybe we can reuse
  703.  *                            the old code too),
  704.  * but we still need to store state info like the dock and workspaces.
  705.  * It is better to keep dock/wspace info in WMState because the user
  706.  * might want to keep the dock configuration while not wanting to
  707.  * resume a previously saved session. 
  708.  * So, wmaker specific state info can be saved in 
  709.  * ~/GNUstep/.AppInfo/WindowMaker/statename.state 
  710.  * Its better to not put it in the defaults directory because:
  711.  * - its not a defaults file (having domain names like wmaker0089504baa
  712.  * in the defaults directory wouldn't be very neat)
  713.  * - this state file is not meant to be edited by users
  714.  * 
  715.  * The old session code will become obsolete. When wmaker is
  716.  * compiled with R6 sm support compiled in, itll be better to
  717.  * use a totally rewritten state saving code, but we can keep
  718.  * the current code for when XSMP_ENABLED is not compiled in. 
  719.  * 
  720.  * This will be confusing to old users (well get lots of "SAVE_SESSION broke!"
  721.  * messages), but itll be better.
  722.  * 
  723.  * -readme
  724.  */
  725.  
  726.  
  727. static char*
  728. getWindowRole(Window window)
  729. {
  730.     XTextProperty prop;
  731.     static Atom atom = 0;
  732.     
  733.     if (!atom)
  734.     atom = XInternAtom(dpy, "WM_WINDOW_ROLE", False);
  735.  
  736.     if (XGetTextProperty(dpy, window, &prop, atom)) {
  737.     if (prop.encoding == XA_STRING && prop.format == 8 && prop.nitems > 0)
  738.         return prop.value;
  739.     }
  740.     
  741.     return NULL;
  742. }
  743.  
  744.  
  745. /*
  746.  *
  747.  * Saved Info:
  748.  *
  749.  * WM_WINDOW_ROLE
  750.  *
  751.  * WM_CLASS.instance
  752.  * WM_CLASS.class
  753.  * WM_NAME
  754.  * WM_COMMAND
  755.  * 
  756.  * geometry
  757.  * state = (miniaturized, shaded, etc)
  758.  * attribute
  759.  * workspace #
  760.  * app state = (which dock, hidden)
  761.  * window shortcut #
  762.  */
  763.  
  764. static proplist_t
  765. makeAppState(WWindow *wwin)
  766. {
  767.     WApplication *wapp;
  768.     proplist_t state;
  769.     WScreen *scr = wwin->screen_ptr;
  770.         
  771.     state = PLMakeArrayWithElements(NULL, NULL);
  772.  
  773.     wapp = wApplicationOf(wwin->main_window);
  774.     
  775.     if (wapp) {
  776.         if (wapp->app_icon && wapp->app_icon->dock) {
  777.  
  778.         if (wapp->app_icon->dock == scr->dock) {
  779.         PLAppendArrayElement(state, PLMakeString("Dock"));
  780.         } else {
  781.         int i;
  782.  
  783.                 for(i=0; i<scr->workspace_count; i++)
  784.                     if(scr->workspaces[i]->clip == wapp->app_icon->dock)
  785.                         break;
  786.  
  787.                 assert(i < scr->workspace_count);
  788.  
  789.                 PLAppendArrayElement(state, 
  790.                      PLMakeString(scr->workspaces[i]->name));
  791.         }
  792.     }
  793.     
  794.     PLAppendArrayElement(state, PLMakeString(wapp->hidden ? "1" : "0"));
  795.     }
  796.     
  797.     return state;
  798. }
  799.  
  800.  
  801.  
  802. Bool
  803. wSessionGetStateFor(WWindow *wwin, WSessionData *state)
  804. {
  805.     char *str;
  806.     proplist_t slist;
  807.     proplist_t elem;
  808.     proplist_t value;
  809.     int index = 0;
  810.  
  811.     index = 3;
  812.  
  813.     /* geometry */
  814.     value = PLGetArrayElement(slist, index++);    
  815.     str = PLGetString(value);
  816.  
  817.     sscanf(str, "%i %i %i %i %i %i", &state->x, &state->y,
  818.        &state->width, &state->height,
  819.        &state->user_changed_width, &state->user_changed_height);
  820.  
  821.  
  822.     /* state */
  823.     value = PLGetArrayElement(slist, index++);
  824.     str = PLGetString(value);
  825.  
  826.     sscanf(str, "%i %i %i", &state->miniaturized, &state->shaded,
  827.        &state->maximized);
  828.  
  829.  
  830.     /* attributes */
  831.     value = PLGetArrayElement(slist, index++);
  832.     str = PLGetString(value);
  833.  
  834.     getAttributeState(str, &state->mflags, &state->flags);
  835.  
  836.  
  837.     /* workspace */
  838.     value = PLGetArrayElement(slist, index++);
  839.     str = PLGetString(value);
  840.  
  841.     sscanf(str, "%i", &state->workspace);
  842.  
  843.  
  844.     /* app state (repeated for all windows of the app) */
  845.     value = PLGetArrayElement(slist, index++);
  846.     str = PLGetString(value);
  847.  
  848.     /* ???? */
  849.  
  850.     /* shortcuts */
  851.     value = PLGetArrayElement(slist, index++);
  852.     str = PLGetString(value);
  853.  
  854.     sscanf(str, "%i", &state->shortcuts);
  855. }
  856.  
  857.  
  858.  
  859. static proplist_t
  860. makeAttributeState(WWindow *wwin)
  861. {
  862.     unsigned int data1, data2;
  863.     char buffer[256];
  864.  
  865. #define W_FLAG(wwin, FLAG)    ((wwin)->defined_user_flags.FLAG \
  866.                     ? (wwin)->user_flags.FLAG : -1)
  867.  
  868.     sprintf(buffer, 
  869.         "%i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i",
  870.         W_FLAG(no_titlebar),
  871.         W_FLAG(no_resizable),
  872.         W_FLAG(no_closable),
  873.         W_FLAG(no_miniaturizable),
  874.         W_FLAG(no_resizebar),
  875.         W_FLAG(no_close_button),
  876.         W_FLAG(no_miniaturize_button),
  877.         /*
  878.         W_FLAG(broken_close),
  879.         W_FLAG(kill_close),
  880.          */
  881.         W_FLAG(no_shadeable),
  882.         W_FLAG(omnipresent),
  883.         W_FLAG(skip_window_list),
  884.         W_FLAG(floating),
  885.         W_FLAG(sunken),
  886.         W_FLAG(no_bind_keys),
  887.         W_FLAG(no_bind_mouse),
  888.         W_FLAG(no_hide_others),
  889.         W_FLAG(no_appicon),
  890.         W_FLAG(dont_move_off),
  891.         W_FLAG(no_focusable),
  892.         W_FLAG(always_user_icon),
  893.         W_FLAG(start_miniaturized),
  894.         W_FLAG(start_hidden),
  895.         W_FLAG(start_maximized),
  896.         W_FLAG(dont_save_session),
  897.         W_FLAG(emulate_appicon));
  898.  
  899.     return PLMakeString(buffer);
  900. }
  901.  
  902.  
  903. static void
  904. appendStringInArray(proplist_t array, char *str)
  905. {
  906.     proplist_t val;
  907.     
  908.     val = PLMakeString(str);
  909.     PLAppendArrayElement(array, val);
  910.     PLRelease(val);
  911. }
  912.  
  913.  
  914. static proplist_t
  915. makeClientState(WWindow *wwin)
  916. {
  917.     proplist_t state;
  918.     proplist_t tmp;
  919.     char *str;
  920.     char buffer[256];
  921.     int i;
  922.     unsigned shortcuts;
  923.  
  924.     state = PLMakeArrayWithElements(NULL, NULL);
  925.     
  926.     /* WM_WINDOW_ROLE */
  927.     str = getWindowRole(wwin->client_win);
  928.     if (!str)
  929.     appendStringInArray(state, "");
  930.     else {
  931.     appendStringInArray(state, str);
  932.     XFree(str);
  933.     }
  934.  
  935.     /* WM_CLASS.instance */
  936.     appendStringInArray(state, wwin->wm_instance);
  937.  
  938.     /* WM_CLASS.class */
  939.     appendStringInArray(state, wwin->wm_class);
  940.  
  941.     /* WM_NAME */
  942.     if (wwin->flags.wm_name_changed)
  943.     appendStringInArray(state, "");
  944.     else
  945.     appendStringInArray(state, wwin->frame->name);
  946.  
  947.     /* geometry */
  948.     sprintf(buffer, "%i %i %i %i %i %i", wwin->frame_x, wwin->frame_y,
  949.         wwin->client.width, wwin->client.height,
  950.         wwin->flags.user_changed_width, wwin->flags.user_changed_height);
  951.     appendStringInArray(state, buffer);
  952.  
  953.     /* state */
  954.     sprintf(buffer, "%i %i %i", wwin->flags.miniaturized,
  955.         wwin->flags.shaded, wwin->flags.maximized);
  956.     appendStringInArray(state, buffer);
  957.  
  958.     /* attributes */
  959.     tmp = makeAttributeState(wwin);
  960.     PLAppendArrayElement(state, tmp);
  961.     PLRelease(tmp);
  962.  
  963.     /* workspace */
  964.     sprintf(buffer, "%i", wwin->frame->workspace);
  965.     appendStringInArray(state, buffer);
  966.  
  967.     /* app state (repeated for all windows of the app) */
  968.     tmp = makeAppState(wwin);
  969.     PLAppendArrayElement(state, tmp);
  970.     PLRelease(tmp);
  971.  
  972.     /* shortcuts */
  973.     shortcuts = 0;
  974.     for (i = 0; i < MAX_WINDOW_SHORTCUTS; i++) {
  975.     if (scr->shortcutWindow[i] == wwin) {
  976.         shortcuts |= 1 << i;
  977.     }
  978.     }
  979.     sprintf(buffer, "%ui", shortcuts);
  980.     appendStringInArray(tmp, buffer);
  981.  
  982.     return state;
  983. }
  984.  
  985.  
  986. static void
  987. smSaveYourselfPhase2Proc(SmcConn smc_conn, SmPointer client_data)
  988. {
  989.     SmProp props[4];
  990.     SmPropValue prop1val, prop2val, prop3val, prop4val;
  991.     char **argv = (char**)client_data;
  992.     int argc;
  993.     int i, j;
  994.     Bool ok = False;
  995.     char *statefile = NULL;
  996.     char *prefix;
  997.     Bool gsPrefix = False;
  998.     char *discardCmd = NULL;
  999.     time_t t;
  1000.     proplist_t state;
  1001.  
  1002. #ifdef DEBUG1
  1003.     puts("received SaveYourselfPhase2 SM message");
  1004. #endif
  1005.  
  1006.     /* save session state */
  1007.     
  1008.     /* the file that will contain the state */
  1009.     prefix = getenv("SM_SAVE_DIR");
  1010.     if (!prefix) {
  1011.     prefix = wusergnusteppath();
  1012.     if (prefix)
  1013.         gsPrefix = True;
  1014.     }
  1015.     if (!prefix) {
  1016.     prefix = getenv("HOME");
  1017.     }
  1018.     if (!prefix)
  1019.     prefix = ".";
  1020.  
  1021.     statefile = malloc(strlen(prefix)+64);
  1022.     if (!statefile) {
  1023.     wwarning(_("out of memory while saving session state"));
  1024.     goto fail;
  1025.     }
  1026.  
  1027.     t = time();
  1028.     i = 0;
  1029.     do {
  1030.     if (gsPrefix)
  1031.         sprintf(statefile, "%s/.AppInfo/WindowMaker/wmaker.%l%i.state",
  1032.             prefix, t, i);
  1033.     else
  1034.         sprintf(statefile, "%s/wmaker.%l%i.state", prefix, t, i);
  1035.     i++;
  1036.     } while (access(F_OK, statefile)!=-1);
  1037.  
  1038.     /* save the states of all windows we're managing */
  1039.     state = PLMakeArrayFromElements(NULL, NULL);
  1040.  
  1041.     /*
  1042.      * Format:
  1043.      *
  1044.      * state_file ::= dictionary with version_info ; state
  1045.      * version_info ::= 'version' = '1';
  1046.      * state ::= 'state' = array of screen_info
  1047.      * screen_info ::= array of (screen number, window_info, window_info, ...)
  1048.      * window_info ::= 
  1049.      */
  1050.     for (i=0; i<wScreenCount; i++) {
  1051.     WScreen *scr;
  1052.     WWindow *wwin;
  1053.     char buf[32];
  1054.     proplist_t pscreen;
  1055.     
  1056.     scr = wScreenWithNumber(i);
  1057.  
  1058.     sprintf(buf, "%i", scr->screen);
  1059.     pscreen = PLMakeArrayFromElements(PLMakeString(buf), NULL);
  1060.  
  1061.     wwin = scr->focused_window;
  1062.     while (wwin) {
  1063.         proplist_t pwindow;
  1064.  
  1065.         pwindow = makeClientState(wwin);
  1066.         PLAppendArrayElement(pscreen, pwindow);
  1067.  
  1068.         wwin = wwin->prev;
  1069.     }
  1070.  
  1071.     PLAppendArrayElement(state, pscreen);
  1072.     }
  1073.  
  1074.     {
  1075.     proplist_t statefile;
  1076.  
  1077.     statefile = PLMakeDictionaryFromEntries(PLMakeString("Version"),
  1078.                         PLMakeString("1.0"),
  1079.  
  1080.                         PLMakeString("Screens"),
  1081.                         state,
  1082.  
  1083.                         NULL);
  1084.  
  1085.     PLSetFilename(statefile, PLMakeString(statefile));
  1086.     PLSave(statefile, NO);
  1087.  
  1088.     PLRelease(statefile);
  1089.     }
  1090.  
  1091.     /* set the remaining properties that we didn't set at
  1092.      * startup time */
  1093.  
  1094.     for (argc=0, i=0; argv[i]!=NULL; i++) {
  1095.     if (strcmp(argv[i], "-clientid")==0
  1096.         || strcmp(argv[i], "-restore")==0) {
  1097.         i++;
  1098.     } else {
  1099.         argc++;
  1100.     }
  1101.     }
  1102.  
  1103.     prop[0].name = SmRestartCommand;
  1104.     prop[0].type = SmLISTofARRAY8;
  1105.     prop[0].vals = malloc(sizeof(SmPropValue)*(argc+4));
  1106.     prop[0].num_vals = argc+4;
  1107.  
  1108.     prop[1].name = SmCloneCommand;
  1109.     prop[1].type = SmLISTofARRAY8;
  1110.     prop[1].vals = malloc(sizeof(SmPropValue)*(argc));
  1111.     prop[1].num_vals = argc;
  1112.  
  1113.     if (!prop[0].vals || !prop[1].vals) {
  1114.     wwarning(_("end of memory while saving session state"));
  1115.     goto fail;
  1116.     }
  1117.  
  1118.     for (j=0, i=0; i<argc+4; i++) {
  1119.     if (strcmp(argv[i], "-clientid")==0
  1120.         || strcmp(argv[i], "-restore")==0) {
  1121.         i++;
  1122.     } else {
  1123.         prop[0].vals[j].value = argv[i];
  1124.         prop[0].vals[j].length = strlen(argv[i]);
  1125.         prop[1].vals[j].value = argv[i];
  1126.         prop[1].vals[j].length = strlen(argv[i]);
  1127.         j++;
  1128.     }
  1129.     }
  1130.     prop[0].vals[j].value = "-clientid";
  1131.     prop[0].vals[j].length = 9;
  1132.     j++;
  1133.     prop[0].vals[j].value = sClientID;
  1134.     prop[0].vals[j].length = strlen(sClientID);
  1135.     j++;
  1136.     prop[0].vals[j].value = "-restore";
  1137.     prop[0].vals[j].length = 11;
  1138.     j++;
  1139.     prop[0].vals[j].value = statefile;
  1140.     prop[0].vals[j].length = strlen(statefile);
  1141.  
  1142.     discardCmd = malloc(strlen(statefile)+8);
  1143.     if (!discardCmd)
  1144.     goto fail;
  1145.     sprintf(discardCmd, "rm %s", statefile);
  1146.     prop[2].name = SmDiscardCommand;
  1147.     prop[2].type = SmARRAY8;
  1148.     prop[2].vals[0] = discardCmd;
  1149.     prop[2].num_vals = 1;
  1150.  
  1151.     SmcSetProperties(sSMCConn, 3, prop);
  1152.  
  1153.     ok = True;
  1154. fail:
  1155.     SmcSaveYourselfDone(smc_conn, ok);
  1156.     
  1157.     if (prop[0].vals)
  1158.     free(prop[0].vals);
  1159.     if (prop[1].vals)
  1160.     free(prop[1].vals);
  1161.     if (discardCmd)
  1162.     free(discardCmd);
  1163.  
  1164.     if (!ok) {
  1165.     remove(statefile);
  1166.     }
  1167.     if (statefile)
  1168.     free(statefile);
  1169. }
  1170.  
  1171.  
  1172. static void
  1173. smSaveYourselfProc(SmcConn smc_conn, SmPointer client_data, int save_type,
  1174.            Bool shutdown, int interact_style, Bool fast)
  1175. {
  1176. #ifdef DEBUG1
  1177.     puts("received SaveYourself SM message");
  1178. #endif
  1179.  
  1180.     if (!SmcRequestSaveYourselfPhase2(smc_conn, smSaveYourselfPhase2Proc,
  1181.                       client_data)) {
  1182.  
  1183.     SmcSaveYourselfDone(smc_conn, False);
  1184.     sWaitingPhase2 = False;
  1185.     } else {
  1186. #ifdef DEBUG1
  1187.     puts("successfull request of SYS phase 2");
  1188. #endif
  1189.     sWaitingPhase2 = True;
  1190.     }
  1191. }
  1192.  
  1193.  
  1194. static void
  1195. smDieProc(SmcConn smc_conn, SmPointer client_data)
  1196. {
  1197. #ifdef DEBUG1
  1198.     puts("received Die SM message");
  1199. #endif
  1200.  
  1201.     wSessionDisconnectManager();
  1202.  
  1203.     Shutdown(WSExitMode, True);
  1204. }
  1205.  
  1206.  
  1207.  
  1208. static void
  1209. smSaveCompleteProc(SmcConn smc_conn)
  1210. {
  1211.     /* it means that we can resume doing things that can change our state */
  1212. #ifdef DEBUG1
  1213.     puts("received SaveComplete SM message");
  1214. #endif
  1215. }
  1216.  
  1217.  
  1218. static void
  1219. smShutdownCancelledProc(SmcConn smc_conn, SmPointer client_data)
  1220. {
  1221.     if (sWaitingPhase2) {
  1222.  
  1223.     sWaitingPhase2 = False;
  1224.  
  1225.     SmcSaveYourselfDone(smc_conn, False);
  1226.     }
  1227. }
  1228.  
  1229.  
  1230. static void
  1231. iceMessageProc(int fd, int mask, void *clientData)
  1232. {
  1233.     IceConn iceConn = (IceConn)clientData;
  1234.  
  1235.     IceProcessMessages(iceConn, NULL, NULL);
  1236. }
  1237.  
  1238.  
  1239. static void
  1240. iceIOErrorHandler(IceConnection ice_conn)
  1241. {
  1242.     /* This is not fatal but can mean the session manager exited.
  1243.      * If the session manager exited normally we would get a 
  1244.      * Die message, so this probably means an abnormal exit.
  1245.      * If the sm was the last client of session, then we'll die
  1246.      * anyway, otherwise we can continue doing our stuff.
  1247.      */
  1248.     wwarning(_("connection to the session manager was lost"));
  1249.     wSessionDisconnectManager();
  1250. }
  1251.  
  1252.  
  1253. void
  1254. wSessionConnectManager(char **argv, int argc)
  1255. {
  1256.     IceConn iceConn;
  1257.     char *previous_id = NULL;
  1258.     char buffer[256];
  1259.     SmcCallbacks callbacks;
  1260.     unsigned long mask;
  1261.     char uid[32];
  1262.     char pid[32];
  1263.     SmProp props[4];
  1264.     SmPropValue prop1val, prop2val, prop3val, prop4val;
  1265.     char restartStyle;
  1266.     int i;
  1267.  
  1268.     mask = SmcSaveYourselfProcMask|SmcDieProcMask|SmcSaveCompleteProcMask
  1269.     |SmcShutdownCancelledProcMask;
  1270.  
  1271.     callbacks.save_yourself.callback = smSaveYourselfProc;
  1272.     callbacks.save_yourself.client_data = argv;
  1273.  
  1274.     callbacks.die.callback = smDieProc;
  1275.     callbacks.die.client_data = NULL;
  1276.  
  1277.     callbacks.save_complete.callback = smSaveCompleteProc;
  1278.     callbacks.save_complete.client_data = NULL;
  1279.  
  1280.     callbacks.shutdown_cancelled.callback = smShutdownCancelledProc;
  1281.     callbacks.shutdown_cancelled.client_data = NULL;
  1282.  
  1283.     for (i=0; i<argc; i++) {
  1284.     if (strcmp(argv[i], "-clientid")==0) {
  1285.         previous_id = argv[i+1];
  1286.         break;
  1287.     }
  1288.     }
  1289.  
  1290.     /* connect to the session manager */
  1291.     sSMCConn = SmcOpenConnection(NULL, NULL, SmProtoMajor, SmProtoMinor,
  1292.                  mask, &callbacks, previous_id,
  1293.                  &sClientID, 255, buffer);
  1294.     if (!sSMCConn) {
  1295.     return;
  1296.     }
  1297. #ifdef DEBUG1
  1298.     puts("connected to the session manager");
  1299. #endif
  1300.  
  1301. /*    IceSetIOErrorHandler(iceIOErrorHandler);*/
  1302.  
  1303.     /* check for session manager clients */
  1304.     iceConn = SmcGetIceConnection(smcConn);
  1305.  
  1306.     if (fcntl(IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC) < 0) {
  1307.     wsyserror("error setting close-on-exec flag for ICE connection");
  1308.     }
  1309.  
  1310.     sSMInputHandler = WMAddInputHandler(IceConnectionNumber(iceConn), 
  1311.                     WIReadMask, iceMessageProc, iceConn);
  1312.  
  1313.     /* setup information about ourselves */
  1314.  
  1315.     /* program name */
  1316.     prop1val.value = argv[0];
  1317.     prop1val.length = strlen(argv[0]);
  1318.     prop[0].name = SmProgram;
  1319.     prop[0].type = SmARRAY8;
  1320.     prop[0].num_vals = 1;
  1321.     prop[0].vals = &prop1val;
  1322.  
  1323.     /* The XSMP doc from X11R6.1 says it contains the user name,
  1324.      * but every client implementation I saw places the uid # */
  1325.     sprintf(uid, "%i", getuid());
  1326.     prop2val.value = uid;
  1327.     prop2val.length = strlen(uid);
  1328.     prop[1].name = SmUserID;
  1329.     prop[1].type = SmARRAY8;
  1330.     prop[1].num_vals = 1;
  1331.     prop[1].vals = &prop2val;
  1332.  
  1333.     /* Restart style. We should restart only if we were running when
  1334.      * the previous session finished. */
  1335.     restartStyle = SmRestartIfRunning;
  1336.     prop3val.value = &restartStyle;
  1337.     prop3val.length = 1;
  1338.     prop[2].name = SmRestartStyleHint;
  1339.     prop[2].type = SmCARD8;
  1340.     prop[2].num_vals = 1;
  1341.     prop[2].vals = &prop3val;
  1342.  
  1343.     /* Our PID. Not required but might be usefull */
  1344.     sprintf(pid, "%i", getpid());
  1345.     prop4val.value = pid;
  1346.     prop4val.length = strlen(pid);
  1347.     prop[3].name = SmProcessID;
  1348.     prop[3].type = SmARRAY8;
  1349.     prop[3].num_vals = 1;
  1350.     prop[3].vals = &prop4val;
  1351.  
  1352.     /* we'll set the rest of the hints later */
  1353.  
  1354.     SmcSetProperties(sSMCConn, 4, props);
  1355.  
  1356. }
  1357.  
  1358.  
  1359. void
  1360. wSessionDisconnectManager(void)
  1361. {
  1362.     if (sSMCConn) {
  1363.     WMDeleteInputHandler(sSMInputHandler);
  1364.     sSMInputHandler = NULL;
  1365.  
  1366.     SmcCloseConnection(sSMCConn, 0, NULL);
  1367.     sSMCConn = NULL;
  1368.     }
  1369. }
  1370.  
  1371. void
  1372. wSessionRequestShutdown(void)
  1373. {
  1374.     /* request a shutdown to the session manager */
  1375.     if (sSMCConn)
  1376.     SmcRequestSaveYourself(sSMCConn, SmSaveBoth, True, SmInteractStyleAny,
  1377.                    False, True);
  1378. }
  1379.  
  1380.  
  1381. Bool 
  1382. wSessionIsManaged(void)
  1383. {
  1384.     return sSMCConn!=NULL;
  1385. }
  1386.  
  1387. #endif /* !XSMP_ENABLED */
  1388.